/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package http.rpc;

import jas.http.client.HttpClient;
import jas.http.request.HttpRequest;
import jas.http.response.HttpResponse;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 *
 * @author Asger
 */
public class RpcClientImpl implements RpcClient {
    
    private HttpClient client;
    private RpcHttpFormatter formatter;
    
    public RpcClientImpl(HttpClient client, RpcHttpFormatter formatter) {
        this.client = client;
        this.formatter = formatter;
    }
    
    public <T> T remoteCall(Class<T> className, String objectId, String methodName, Object[] params) 
            throws RemoteException {
        
        HttpRequest request = formatter.writeRequest(objectId, methodName, params);
        HttpResponse response = null;
        try {
            response = client.sendRequest(request);
        } catch (IOException ex) {
            throw new ConnectionLostException(ex);
        }
        RpcResponse r = formatter.parseResponse(response);
        
        if (r.isException()) {
            throw new RemoteException(r.getException());
        }
        
        Object result = r.getResponse();
        
        // If the object was sent by reference, convert it to the stub class
        if (result instanceof RemoteObject) {
            try {
                RemoteObject remote = (RemoteObject) result;
                
                return getRemote(className, remote);
            } catch (Exception ex) {
                throw new RemoteException(ex);
            }
        }
        
        // Object was sent by value, just return it
        return (T) result;
    }
    
    private <T> T getRemote(Class<T> className, RemoteObject remote) {
        try {
            Class clazz = Class.forName(className.getName() + "_Stub");
            Constructor c = clazz.getConstructor(RemoteObject.class, RpcClient.class);
            Object b = c.newInstance(remote, this);
            return (T) b;
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException("Stub class for " + className.getSimpleName() + " is missing: " + ex.getMessage());
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException("Constructor for stub class " + className.getSimpleName() + " is missing. It must take a RemoteObject and a RpcClient.");
        } catch (InstantiationException ex) {
            throw new RuntimeException("Stub class " + className.getSimpleName() + " is abstract.");
        } catch (IllegalAccessException ex) {
            throw new RuntimeException("Stub class " + className.getSimpleName() + "'s constructor was not public.");
        } catch (InvocationTargetException ex) {
            throw new RuntimeException("Stub class " + className.getSimpleName() + "'s constructor threw exception: " + ex);
        }
    }
    
    public <T> T getRemote(Class<T> className, String id) {
        return getRemote(className, new RemoteObject(id));
    }
    
}
